查看原文
其他

使用jbox2d物理引擎打造摩拜单车贴纸动画效果

2017-07-12 Kimi 终端研发部


前言介绍

 一个仿摩拜单车贴纸动画绚丽效果,来自Kimi的投稿,使用jbox2d物理引擎打精美的View特效 

Kimi的博客地址:

http://www.itkimi.com/2017/07/09/mobiketags/

正文 

继上一次实现了ofo小黄人斗鸡眼效果(眼珠跟随手机加速器自动滚动),想看效果的朋友,请点击这篇文章 :

几行代码实现高仿ofo—小黄车APP首页眼睛跟随加速器移动酷炫效果 

接下来我们在看下摩拜单车安卓客户端贴纸动画,是什么样子呢?先上一副动态图: 

  • 效果很炫,也很漂亮,至少看起来实现的难度要比ofo那个大的多。那么该怎么实现呢?(这里插下题外话,ios端比较容易实现,因为系统内置了某些api,可以很方便的实现这样的效果),可我们是安卓程序员啊,怎么办?自定义view?嗯,好像可以,但是这坐标怎么计算?并且还持续运动着,还有弹力,摩擦力,碰撞力…..

大脑一片茫然,如果产品经理坚决的确定需要这个效果,咋整?先不慌,我们可以先了解下摩拜单车是如何实现这样的效果的,很不幸,经过反编译摩拜单车apk后,安装包进行了加固,连混淆后的代码都看不到.

于是就上网各种搜索酷炫动画,不经意间发现了jbox2d物理引擎,然后回头在看下摩拜单车的apk,发现了其用到了libgdx-box2d,所以说摩拜是使用libgdx-box2d物理引擎来实现的这个效果:

jbox2d和libgdx-box2d:

  • jbox2d和libgdx-box2d有什么区别,他们之间的关系是什么?

jbox2d:

  • jbox2d百度百科这样描述的: jbox2D物理引擎原版 Box2D 是采用C编写的,后来扩展到java,as等多种版本。著名手机游戏愤怒的小鸟便是采用jbox2D物理引擎。不过java版的jbox2D引擎性能不如C环境下运行的性能好。在性能配置比较好的手机上面,jbox2D效果也是不错的。

libgdx:

  • libgdx百度百科这样描述的:libGdx是一个跨平台的2D/3D的游戏开发框架,它由Java/C/C++语言编写而成。

可能看完百度百科,还是比较模糊,我们只知道了他俩都是物理引擎,并且知道了愤怒的小鸟,就是用jbox2d引擎开发的。

后来经过搜索了解得出这样一句话:Libgdx使用jni封装了box2d的c版本,使得其运行效率比其他同级的物理引擎如jbox2d快不少。所以最后的结论是jbox2d是面向java的,运行效率要慢。而libgdx是面向c/c的,运行效率要快。

  • 这次效果的实现是基于jbox2d上完成的,最终效果图如下:

APK 下载体验: 

box2d快速上手,推荐参考百度文库 :

https://wenku.baidu.com/view/c584cbfaf90f76c661371a5f.html 

网上的文档都是比较过时的,新版中有些地方有变化,不过不影响快速入门。

jbox2d :

Github: https://github.com/jbox2d/jbox2d

 简单描述下jbox2d物理引擎在安卓中的用法,jbox2d物理引擎并不负责view的绘制,只负物理数据的计算和分析,如物体的密度,质量,摩擦力,速度,碰撞力,恢复力……

通俗的讲,在安卓中一个view代表了一个物体,也就是刚体,通过view和刚体的绑定并设置初始参数,最后不停的draw,再从绑定的刚体中取出参数绘制到界面上,如此循环。而物体的所有物理参数都由jbox2d物理引擎帮助我们完成。


在MobikeLibrary中,自己只写了两个类,就实现了如上效果

Mobike和MobikeView是自己写的,org.jbox2d是官方的源代码,MobikeLibrary的具体使用请查看 https://github.com/andmizi/MobikeTags

MobikeView.java:

package com.mobike.library;import android.content.Context;import android.graphics.Canvas;import android.support.annotation.NonNull;import android.support.annotation.Nullable;import android.util.AttributeSet;import android.widget.FrameLayout;/** * Created by kimi on 2017/7/8 0008. * Email: 24750@163.com */public class MobikeView extends FrameLayout {    
       private Mobike mMobike;    
       public MobikeView(@NonNull Context context) {        
           this(context,null);        }    
       
       public MobikeView(@NonNull Context context, @Nullable AttributeSet attrs) {        
           super(context, attrs);            setWillNotDraw(false);            mMobike = new Mobike(this);        }
           
            
       @Override        protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);            mMobike.onSizeChanged(w,h);        }
          
       @Override        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {        super.onLayout(changed, left, top, right, bottom);            mMobike.onLayout(changed);        }  
         
       @Override        protected void onDraw(Canvas canvas) {        
           super.onDraw(canvas);            mMobike.onDraw(canvas);        }    
       
       public Mobike getmMobike(){        
       return this.mMobike;        } } MobikeView继承自Framelayout,比较简单,这里略过

Mobike.java

package com.mobike.library; import android.graphics.Canvas; import android.util.Log; import android.view.View; import android.view.ViewGroup; import org.jbox2d.collision.shapes.CircleShape; import org.jbox2d.collision.shapes.PolygonShape; import org.jbox2d.collision.shapes.Shape; import org.jbox2d.common.Vec2; import org.jbox2d.dynamics.Body; import org.jbox2d.dynamics.BodyDef; import org.jbox2d.dynamics.BodyType; import org.jbox2d.dynamics.FixtureDef; import org.jbox2d.dynamics.World; import java.util.Random;/** * Created by kimi on 2017/7/8 0008. * Email: 24750@163.com */

public class Mobike {    
   public static final String TAG = Mobike.class.getSimpleName();  
     private World world;    
    private float dt = 1f / 60f;  
      private int velocityIterations = 3;  
       private int positionIterations = 10;  
    private float friction = 0.3f,density = 0.5f,restitution = 0.3f,ratio = 50;    private int width,height;    private boolean enable = true;    private final Random random = new Random();    private ViewGroup mViewgroup;    public Mobike(ViewGroup viewgroup) {        this.mViewgroup = viewgroup;            density = viewgroup.getContext().getResources().getDisplayMetrics().density;    }  
    
    public void onSizeChanged(int width,int height){    
         this.width = width;        this.height = height; //sizeChanged的时候获取到viewgroup的宽和高    }    
         
    public void onDraw(Canvas canvas) {        
            if(!enable){ //设置标记,在界面可见的时候开始draw,在界面不可见的时候停止draw            return;    } //dt 更新引擎的间隔时间 //velocityIterations 计算速度 //positionIterations 迭代的次数        world.step(dt,velocityIterations,positionIterations);        int childCount = mViewgroup.getChildCount();        for(int i = 0; i < childCount; i++){            View view = mViewgroup.getChildAt(i);            Body body = (Body) view.getTag(R.id.mobike_body_tag);            if(body != null){ //从view中获取绑定的刚体,取出参数,开始更新view                view.setX(metersToPixels(body.getPosition().x) - view.getWidth() / 2);                view.setY(metersToPixels(body.getPosition().y) - view.getHeight() / 2);                view.setRotation(radiansToDegrees(body.getAngle() % 360));            }        } //手动调用,反复执行draw方法        mViewgroup.invalidate();    }    
   
   public void onLayout(boolean changed) {        createWorld(changed);    }    
   
   public void onStart(){        setEnable(true);    }    public void onStop(){        setEnable(false);    }    
   
   public void update(){        world = null;        onLayout(true);    }    
   
   private void createWorld(boolean changed) { //jbox2d中world称为世界,这里创建一个世界        if(world == null){            world = new World(new Vec2(0, 10.0f)); //创建边界,注意边界为static静态的,当物体触碰到边界,停止模拟该物体            createTopAndBottomBounds();            createLeftAndRightBounds();        }        int childCount = mViewgroup.getChildCount();        for(int i = 0; i < childCount; i++){            View view = mViewgroup.getChildAt(i);            Body body = (Body) view.getTag(R.id.mobike_body_tag);            if(body == null || changed){                createBody(world,view);            }        }    }    
   
   private void createBody(World world, View view) { //创建刚体描述,因为刚体需要随重力运动,这里type设置为DYNAMIC        BodyDef bodyDef = new BodyDef();        bodyDef.setType(BodyType.DYNAMIC); //设置初始参数,为view的中心点        bodyDef.position.set(pixelsToMeters(view.getX() + view.getWidth() / 2) ,                             pixelsToMeters(view.getY() + view.getHeight() / 2));        Shape shape = null;        Boolean isCircle = (Boolean) view.getTag(R.id.mobike_view_circle_tag);        if(isCircle != null && isCircle){ //创建圆体形状            shape = createCircleShape(view);        }else{ //创建多边形形状            shape = createPolygonShape(view);        } //初始化物体信息 //friction  物体摩擦力 //restitution 物体恢复系数 //density 物体密度        FixtureDef fixture = new FixtureDef();        fixture.setShape(shape);        fixture.friction = friction;        fixture.restitution = restitution;        fixture.density = density; //用世界创建出刚体        Body body = world.createBody(bodyDef);        body.createFixture(fixture);        view.setTag(R.id.mobike_body_tag,body); //初始化物体的运动行为        body.setLinearVelocity(new Vec2(random.nextFloat(),random.nextFloat()));    }    
   
   private Shape createCircleShape(View view){        CircleShape circleShape = new CircleShape();        circleShape.setRadius(pixelsToMeters(view.getWidth() / 2));        return circleShape;    }    
   
   private Shape createPolygonShape(View view){        PolygonShape polygonShape = new PolygonShape();        polygonShape.setAsBox(pixelsToMeters(view.getWidth() / 2),pixelsToMeters(view.getHeight() / 2));        return polygonShape;    }    
   
   private void createTopAndBottomBounds() {        BodyDef bodyDef = new BodyDef();        bodyDef.type = BodyType.STATIC;        PolygonShape box = new PolygonShape();        
        float boxWidth = pixelsToMeters(width);      
        float boxHeight =  pixelsToMeters(ratio);        box.setAsBox(boxWidth, boxHeight);        FixtureDef fixtureDef = new FixtureDef();        fixtureDef.shape = box;        fixtureDef.density = 0.5f;        fixtureDef.friction = 0.3f;        fixtureDef.restitution = 0.5f;        bodyDef.position.set(0, -boxHeight);        Body topBody = world.createBody(bodyDef);        topBody.createFixture(fixtureDef);        bodyDef.position.set(0, pixelsToMeters(height)+boxHeight);        Body bottomBody = world.createBody(bodyDef);        bottomBody.createFixture(fixtureDef);    }    
   
   private void createLeftAndRightBounds() {      
         float boxWidth = pixelsToMeters(ratio);        
         float boxHeight = pixelsToMeters(height);        BodyDef bodyDef = new BodyDef();        bodyDef.type = BodyType.STATIC;        PolygonShape box = new PolygonShape();        box.setAsBox(boxWidth, boxHeight);        FixtureDef fixtureDef = new FixtureDef();        fixtureDef.shape = box;        fixtureDef.density = 0.5f;        fixtureDef.friction = 0.3f;        fixtureDef.restitution = 0.5f;        bodyDef.position.set(-boxWidth, boxHeight);        Body leftBody = world.createBody(bodyDef);        leftBody.createFixture(fixtureDef);        bodyDef.position.set(pixelsToMeters(width) + boxWidth, 0);        Body rightBody = world.createBody(bodyDef);        rightBody.createFixture(fixtureDef);    }    
   
   private float radiansToDegrees(float radians) {        
       return radians / 3.14f * 180f;    }    
   
   private float degreesToRadians(float degrees){        
       return (degrees / 180f) * 3.14f;    }    
   
   public float metersToPixels(float meters) {        
       return meters * ratio;    }    
   
   public float pixelsToMeters(float pixels) {        
       return pixels / ratio;    }    
   
   public void random() { //弹一下,模拟运动        int childCount = mViewgroup.getChildCount();        
           for (int i = 0; i < childCount; i++) {            Vec2 impulse = new Vec2(random.nextInt(1000) - 1000, random.nextInt(1000) - 1000);            View view = mViewgroup.getChildAt(i);            Body body = (Body) view.getTag(R.id.mobike_body_tag);            if(body != null){                body.applyLinearImpulse(impulse, body.getPosition(),true);            }        }    }    
   
   public void onSensorChanged(float x,float y) { //传感器模拟运动        int childCount = mViewgroup.getChildCount();        
           for (int i = 0; i < childCount; i++) {            Vec2 impulse = new Vec2(x, y);            View view = mViewgroup.getChildAt(i);            Body body = (Body) view.getTag(R.id.mobike_body_tag);            if(body != null){                body.applyLinearImpulse(impulse, body.getPosition(),true);            }        }    }    
           
     public float getFriction() {        
             return friction;    }    
   
    public void setFriction(float friction) {        
            if(friction >= 0){            
            this.friction = friction;        }    }    
 
   public float getDensity() {        
            return density;    }    
   
   public void setDensity(float density) {
           if(density >= 0){  
              this.density = density;        }    }    
   
   public float getRestitution() {
           return restitution;    }    
   
   public void setRestitution(float restitution) {
           if(restitution >= 0){            
           this.restitution = restitution;        }    }    
   
   public float getRatio() {
           return ratio;    }    
   
   public void setRatio(float ratio) {
           if(ratio >= 0){            
           this.ratio = ratio;        }    }    
   
   public boolean getEnable() {      
    return enable;    }    
   
   public void setEnable(boolean enable) {
        this.enable = enable;        mViewgroup.invalidate();    } }

阅读顺序,onlayout—ondraw,如果你仔细阅读过上面的百度文库,应该都可以看的懂代码。

Github项目地址:

https://github.com/andmizi/MobikeTags

个人博客:

http://www.itkimi.com

终端研发部提倡 没有做不到的,只有想不到的。

在这里获得的不仅仅是技术! 


让心,在阳光下学会舞蹈

让灵魂,在痛苦中学会微笑

—终端研发部—



如果你觉得此文对您有所帮助,欢迎入群 QQ交流群 :232203809   

微信公众号:终端研发部


            

这里学到的不仅仅是技术


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存